// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_shadow_TSM.h"

namespace MK3DE
{
	CShadowTSM::CShadowTSM()
	{
		_srcPercent = 0.1f;
		_dstPercent = 0.8f;

		_topLeftIdx = 0;
		_topRightIdx = 0;
		_bottomLeftIdx = 0;
		_bottomRightIdx = 0;
		_topIdx = 0;
		_bottomIdx = 0;
		_nearIdx = 0;
		_farIdx = 0;
	}

	CShadowTSM::~CShadowTSM()
	{
	}

	void CShadowTSM::__TransformFrustumToWorldSpace(const D3DXMATRIX& matViewProj, D3DXVECTOR3* frustumWorld)
	{
		D3DXVECTOR3 frustum[] = {
			D3DXVECTOR3(-1, 1, 0),
			D3DXVECTOR3(1, 1, 0),
			D3DXVECTOR3(1, -1, 0),
			D3DXVECTOR3(-1, -1, 0),
			D3DXVECTOR3(-1, 1, 1.0f),
			D3DXVECTOR3(1, 1, 1.0f),
			D3DXVECTOR3(1, -1, 1.0f),
			D3DXVECTOR3(-1, -1, 1.0f),
		};

		D3DXMATRIX matInvViewProj;
		D3DXMatrixInverse(&matInvViewProj, NULL, &matViewProj);
		D3DXVec3TransformCoordArray(frustumWorld, sizeof(D3DXVECTOR3), frustum, sizeof(D3DXVECTOR3), &matInvViewProj, 8);
	}

	void CShadowTSM::__CalcMatrixLightViewProj(D3DXVECTOR3& lightDir, D3DXVECTOR3* frustum)
	{
		D3DXVECTOR3 bottomCenter = (frustum[0] + frustum[1] + frustum[2] + frustum[3]) * 0.25f;
		D3DXVECTOR3 topCenter = (frustum[4] + frustum[5] + frustum[6] + frustum[7]) * 0.25f;
		D3DXVECTOR3 centerLine = topCenter - bottomCenter;
		D3DXVec3Normalize(&centerLine, &centerLine);

		D3DXMATRIX matLightView;
		D3DXVECTOR3 centerPoint(0, 0, 0);
		D3DXMatrixLookAtLH(&matLightView, &centerPoint, &(centerPoint + lightDir), &centerLine);
		D3DXVec3TransformCoordArray(frustum, sizeof(D3DXVECTOR3), frustum, sizeof(D3DXVECTOR3), &matLightView, 8);

		__Calc2DConvexCullBorder(frustum);

		float left = min(frustum[_topLeftIdx].x, frustum[_bottomLeftIdx].x);
		float right = max(frustum[_topRightIdx].x, frustum[_bottomRightIdx].x);
		float top = frustum[_topIdx].y;
		float bottom = frustum[_bottomIdx].y;
		float znear = frustum[_nearIdx].z;
		float zfar = frustum[_farIdx].z;

		D3DXMATRIX matLightProj;
		D3DXMatrixOrthoOffCenterLH(&matLightProj, left, right, bottom, top, znear, zfar);
		D3DXVec3TransformCoordArray(frustum, sizeof(D3DXVECTOR3), frustum, sizeof(D3DXVECTOR3), &matLightProj, 8);

		_matLightViewProj = matLightView * matLightProj;
	}

	void CShadowTSM::__Calc2DConvexCullBorder(D3DXVECTOR3* frustum)
	{
		float topLeft = frustum[0].x;
		float topRight = frustum[0].x;

		float bottomLeft = frustum[0].y;
		float bottomRight = frustum[0].y;

		float top = frustum[0].y;
		float bottom = frustum[0].y;
		float znear = frustum[0].z;
		float zfar = frustum[0].z;

		_topLeftIdx = 0;
		_topRightIdx = 0;
		_bottomLeftIdx = 0;
		_bottomRightIdx = 0;
		_topIdx = 0;
		_bottomIdx = 0;
		_nearIdx = 0;
		_farIdx = 0;

		for (int i = 1; i < 4; i++)
		{
			if (frustum[i].x < bottomLeft) { bottomLeft = frustum[i].x; _bottomLeftIdx = i; }
			if (frustum[i].x > bottomRight) { bottomRight = frustum[i].x; _bottomRightIdx = i; }

			if (frustum[i].y > top) { top = frustum[i].y; _topIdx = i; }
			if (frustum[i].y < bottom) { bottom = frustum[i].y; _bottomIdx = i; }
			if (frustum[i].z < znear) { znear = frustum[i].z; _nearIdx = i; }
			if (frustum[i].z > zfar) { zfar = frustum[i].z; _farIdx = i; }
		}

		for (int i = 4; i < 8; i++)
		{
			if (frustum[i].x < topLeft) { topLeft = frustum[i].x; _topLeftIdx = i; }
			if (frustum[i].x > topRight) { topRight = frustum[i].x; _topRightIdx = i; }

			if (frustum[i].y > top) { top = frustum[i].y; _topIdx = i; }
			if (frustum[i].y < bottom) { bottom = frustum[i].y; _bottomIdx = i; }
			if (frustum[i].z < znear) { znear = frustum[i].z; _nearIdx = i; }
			if (frustum[i].z > zfar) { zfar = frustum[i].z; _farIdx = i; }
		}
	}

	void CShadowTSM::__CalcTrapezoidal(D3DXVECTOR3* frustum)
	{
		// calculate intersection and translate it to origin.
		float left = min(frustum[_topLeftIdx].x, frustum[_bottomLeftIdx].x);
		float right = max(frustum[_topRightIdx].x, frustum[_bottomRightIdx].x);
		float src = 2.0f * _srcPercent;
		float e = 1 - 2 * _dstPercent;
		float n = 2.0f * src * (1 + e) / (2.0f - 2 * src - 2.0f * e);
		float y = -1.0f - n;

		// calculate T2.
		D3DXMATRIX matT2;
		D3DXMatrixTranslation(&matT2, 0, -y, 0);
		D3DXVec3TransformCoordArray(frustum, sizeof(D3DXVECTOR3), frustum, sizeof(D3DXVECTOR3), &matT2, 8);

		// construct the trapezoidal.
		float k1 = frustum[_topLeftIdx].y / frustum[_topLeftIdx].x;
		float k2 = frustum[_bottomLeftIdx].y / frustum[_bottomLeftIdx].x;
		float kLeft = max(k1, k2);

		k1 = frustum[_topRightIdx].y / frustum[_topRightIdx].x;
		k2 = frustum[_bottomRightIdx].y / frustum[_bottomRightIdx].x;
		float kRight = min(k1, k2);

		D3DXVECTOR3 trapezoidal[] = {
			D3DXVECTOR3(frustum[_topIdx].y / kLeft, frustum[_topIdx].y, 0),
			D3DXVECTOR3(frustum[_bottomIdx].y / kLeft, frustum[_bottomIdx].y, 0),
			D3DXVECTOR3(frustum[_topIdx].y / kRight, frustum[_topIdx].y, 0),
			D3DXVECTOR3(frustum[_bottomIdx].y / kRight, frustum[_bottomIdx].y, 0),
		};

		// calculate H.
		D3DXVECTOR3 u = (trapezoidal[1] + trapezoidal[3]) * 0.5f;
		D3DXMATRIX matH(
			1.0f, 0, 0, 0,
			-u.x / u.y, 1.0f, 0, 0,
			0, 0, 1.0f, 0,
			0, 0, 0, 1.0f);
		D3DXVec3TransformCoordArray(trapezoidal, sizeof(D3DXVECTOR3), trapezoidal, sizeof(D3DXVECTOR3), &matH, 4);

		// calculate S.
		D3DXMATRIX matS(
			1.0f / trapezoidal[3].x, 0, 0, 0,
			0, 1.0f / trapezoidal[3].y, 0, 0,
			0, 0, 1.0f, 0,
			0, 0, 0, 1.0f);
		D3DXVec3TransformCoordArray(trapezoidal, sizeof(D3DXVECTOR3), trapezoidal, sizeof(D3DXVECTOR3), &matS, 4);

		// calculate N.
		D3DXMATRIX matN(
			1.0f, 0, 0, 0,
			0, 1.0f, 0, 1.0f,
			0, 0, 1.0f, 0,
			0, 1.0f, 0, 0.0f);
		D3DXVec3TransformCoordArray(trapezoidal, sizeof(D3DXVECTOR3), trapezoidal, sizeof(D3DXVECTOR3), &matN, 4);

		// calculate T3.
		D3DXMATRIX matT3(
			1.0f, 0, 0, 0,
			0, 1.0f, 0, 0,
			0, 0, 1.0f, 0,
			0, -(trapezoidal[0].y + trapezoidal[1].y) * 0.5f, 0, 1.0f);
		D3DXVec3TransformCoordArray(trapezoidal, sizeof(D3DXVECTOR3), trapezoidal, sizeof(D3DXVECTOR3), &matT3, 4);

		// calculate S2.
		D3DXMATRIX matS2(
			1.0f, 0, 0, 0,
			0, 1.0f / trapezoidal[3].y, 0, 0,
			0, 0, 1.0f, 0,
			0, 0, 0, 1.0f);
		D3DXVec3TransformCoordArray(trapezoidal, sizeof(D3DXVECTOR3), trapezoidal, sizeof(D3DXVECTOR3), &matS2, 4);
		
		// calculate the final trapezoidal transformation.
		_matTrapezoidal = matT2 * matH * matS * matN * matT3 * matS2;
	}

	void CShadowTSM::SetFocusPercent(float srcPercent, float dstPercent)
	{
		_srcPercent = srcPercent;
		_dstPercent = dstPercent;
	}

	void CShadowTSM::CalcTrapezoidalTransformation(const D3DXMATRIX& matViewProj, D3DXVECTOR3& lightDir)
	{
		D3DXVECTOR3 frustum[8];
		__TransformFrustumToWorldSpace(matViewProj, frustum);

		memcpy(_frustum, frustum, sizeof(D3DXVECTOR3) * 8);

		__CalcMatrixLightViewProj(lightDir, frustum);
		__CalcTrapezoidal(frustum);

		if (GetAsyncKeyState(VK_F1))
			D3DXMatrixIdentity(&_matTrapezoidal);

		D3DXMATRIX mat = _matLightViewProj * _matTrapezoidal;

		D3DXVec3TransformCoordArray(_frustum, sizeof(D3DXVECTOR3), _frustum, sizeof(D3DXVECTOR3), &mat, 8);

		for (int i = 0; i < 8; ++i)
		{
			_frustum[i].x = (_frustum[i].x + 1.0f) * 0.5f * 256;
			_frustum[i].y = (1.0f - _frustum[i].y) * 0.5f * 256;
		}
	}
}
